<?php /** MenuHelper 1.1
 *
 * The purpose of this helper is to generate menus and other lists of links. The dynamic api
 * lets you build any amount of multi level "menus". Created for the purpose of main, sub and
 * context sensitive menues, this helper can also be used as an html UL generator.
 * 
 * Installation and requirements:
 * 
 * - Copy this file to app/views/helpers
 * - add 'Menu' to AppController's $helpers property
 *
 * Description :
 * 
 * To understand how this helper works there are two important concepts. Firstly, in a single
 * run of cakephp, only one instance of any helper is used. There for we can temporarily "store"
 * information in it (as a property) between views, elements and layouts. In the most common
 * use of this helper, links are created in the view and in elements and then the layout renders 
 * them. The reason why this works is the second important concept; the layout is rendered after
 * the view. Therefore we can add to the list of urls in when the layout is rendered, the menu
 * helper already know all that it is to render. 
 * 
 * Usage example 1: Creating a list and rendering it
 * 
 * //We have a list of links stored in the database and on this view we wish to list them out.
 * // A single link array looks like this : array(Link => array(url,title))
 * 
 * foreach ($links as $link) {
 *         $menu->add('link_list',array($link['title'],$link['url']));
 * }
 * echo $menu->generate('link_list');
 * 
 * Usage example 2: Replacing the baked html links with a context sensitive menu
 * 
 * A typical use is compromised of several $menu->add( and a single echo $menu->generate(
 * 
 * For instance to add a context sensitive menu to replace the $html->link() baked by cake
 * in default follow these steps:
 * 
 * 1. In app/views/layouts/default.ctp just inside the #content div add this line :
 *      if (isset($menu)) { echo $menu->generate('context'); }
 * 2. Copy the files from cake/console/libs/templates/views to app/vendors/shells/templates/views
 * 3. Inside those 3 files replace 
 *      "echo $html->link("
 *         with
 *      "$menu->add('context', array("
 *         and
 *    add a ) to the end.
 * 4. Remove the div, ul and li parts that surrounded these links.
 * 5. Add this css rule ul.menu_context li { list-style: none; }
 * 
 * Now all these links that used to be echoed in the view, will be printed as a ul on top of the layout.
 *
 * Usage example 3: A multilevel list 
 * 
 * //Say we have an Article with hasMany Page, to render a list of links to both we could do :  
 * 
 * foreach ($data as $article) {
 *         $menu->add('articles', array($article['Article']['title'], 
 *                 array('action'=>'view', $article['Article']['id'])));
 *         foreach ($article['Page'] as $page) {
 *             $menu->add(array('articles', $article['Article']['id']), array(
 *                 $page['Page']['title'], array('controller'=> 'pages',
 *                 'action' => 'view', $page['Page']['id'])));
 *         }
 * }
 * echo $menu->generate('articles');
 * 
 * This will genreate this :
 * 
 * <ul>
 * <li><a href="/articles/view/1">Article 1</a></li>
 * <li><ul>
 *     <li><a href="/pages/view/1">Page 1</a></li>
 *     <li><a href="/pages/view/2">Page 2</a></li>
 * </ul></li>
 * <li><a href="/articles/view/2">Article 2</a></li>
 * <li><ul>
 *     <li><a href="/pages/view/3">Page 3</a></li>
 *     <li><a href="/pages/view/4">Page 4</a></li>
 * </ul></li>
 * </ul>
 * 
 * 
 * Customizations : 
 * 
 * If you wish to style the menus, take a look at the generated source code, each UL level
 * is given a unique class based on the target name. If you have need of more fine control,
 * you can use the $options paramter of the helpers methods to use image icons, class on
 * the A tags, id, class or style LI, UL and DIVs. See each method for specifics.
 *  
 * @author Ronny Vindenes
 * @author Alexander Morland
 * @license MIT
 * @modified 5.feb 2009
 * @version 1.1
 */
class MenuHelper extends AppHelper {
    
    var $helpers = array('Html');
    
    var $items = array('main' => array());
    
    /**
     * Adds a menu item to a target location
     *
     * 
     * @param mixed $target String or Array target notations
     * @param array $link Array in same format as used by HtmlHelper::link()
     * @param array $options
     *  @options 'icon'  > $html->image() params
     *  @options 'class' > <a class="?">
     *  @options 'li'    > string:class || array('id','class','style')
     *  @options 'div'     > string:class || boolean:use || array('id','class','style') 
     * 
     * @return boolean successfully added
     */
    function add($target = 'main', $link = array(), $options = array()) {
        
        if (!is_array($link) || !is_array($options) || !isset($link[0]) || !(is_array($link[0]) || is_string($link[0]))) {
            return false;
        }
        
        if (!isset($link[1])) {
            $link[1] = array();
        }
        
        if (!isset($link[2])) {
            $link[2] = array();
        }
        
        if (!isset($link[3])) {
            $link[3] = false;
        }
        
        if (!isset($link[4])) {
            $link[4] = true;
        }
        
        if (is_array($target)) {
            
            $depth = count($target);
            $menu = &$this->items;
            
            for ($i = 0; $i < $depth; $i++) {
                if (array_key_exists($target[$i], $menu)) {
                    $menu = &$menu[$target[$i]];
                } else {
                    $menu[$target[$i]] = array(true);
                    $menu = &$menu[$target[$i]];
                }
            }
        
        } else {
            $menu = &$this->items[$target];
        }
        
        $menu[] = array($link, $options);
        
        return true;
    }
    
    /**
     * Adds an element to a target item
     *
     * @param mixed $target String or Array target notations
     * @param string $element Any string
     * @param array $options
     *  @options 'li'    > string:class || array('id','class','style')
     *  @options 'div'     > string:class || boolean:use || array('id','class','style') 
     * 
     * @return boolean successfully added
     */
    function addElement($target = 'main', $element = false, $options = array()) {
        
        if ($element === false) {
            return false;
        }
        
        if (is_array($target)) {
            
            $depth = count($target);
            $menu = &$this->items;
            
            for ($i = 0; $i < $depth; $i++) {
                if (array_key_exists($target[$i], $menu)) {
                    $menu = &$menu[$target[$i]];
                } else {
                    $menu[$target[$i]] = array(true);
                    $menu = &$menu[$target[$i]];
                }
            }
        
        } else {
            $menu = &$this->items[$target];
        }
        
        $menu[] = array(1 => $options, 2 => $element);
        
        return true;
    }
    
    /**
     * Renders and returns the generated html for the targeted item and its element and children
     *
     * @param mixed $source String or Array target notations
     * @param array $options
     *  @options 'style' > string:predefined style name || boolean:use
     *  @options 'class' > <ul class="?"><li><ul>..</li></ul>
     *  @options 'id'      > <ul id="?"><li><ul>..</li></ul>
     *  @options 'ul'    > string:class || array('class','style')
     *  @options 'div'     > string:class || boolean:use || array('id','class','style') 
     *  @options 'active'> array('tag' => string(span,strong,etc), 'attributes' => array(htmlAttributes)) 
     *
     * @example echo $menu->generate('context', array('active' => array('tag' => 'a','attributes' => array('style' => 'color:red;','id'=>'current'))));
     * @return mixed string generated html or false if target doesnt exist
     */
    function generate($source = 'main', $options = array()) {
        
        $out = '';
        $list = '';
        
        $ulAttributes = array();
        
        /* DOM class attribute for outer UL */
        if (isset($options['class'])) {
            $ulAttributes['class'] = $options['class'];
        } else {
            if (is_array($source)) {
                $ulAttributes['class'] = 'menu_' . $source[count($source) - 1];
            } else {
                $ulAttributes['class'] = 'menu_' . $source;
            }
        }
        
        /* DOM element id for outer UL */
        if (isset($options['id'])) {
            $ulAttributes['id'] = $options['id'];
        }
        
        /* Find source menu */
        if (is_array($source)) {
            
            $depth = count($source);
            $menu = &$this->items;
            
            for ($i = 0; $i < $depth; $i++) {
                if (array_key_exists($source[$i], $menu)) {
                    $menu = &$menu[$source[$i]];
                } else {
                    return false;
                }
            }
        
        } else {
            if (!isset($this->items[$source])) {
                return false;
            }
            $menu = &$this->items[$source];
        }
        if (isset($options['active'])) {
            $defaults = array( 'tag' => 'span', 'attributes' =>  array('class' => 'active'));
            $options['active'] = array_merge($defaults, $options['active']);
        }
        
        /* Generate menu items */
        foreach ($menu as $key => $item) {
            $liAttributes = array();
            $aAttributes = array();
            
            if (isset($item[1]['li'])) {
                $liAttributes = $item[1]['li'];
            }
            
            if (isset($item[0]) && $item[0] === true) {
                $menusource = $source;
                if (!is_array($menusource)) {
                    $menusource = array($menusource);
                }
                $menusource[] = $key;
                /* Don't set DOM element id on sub menus */
                if (isset($options['id'])) {
                    unset($options['id']);
                }
                $listitem = $this->generate($menusource, $options);
                if (empty($listitem)) {
                    continue;
                }
            } elseif (isset($item[0])) {
                if (!isset($item[0][2]['title'])) {
                    $item[0][2]['title'] = $item[0][0];
                }
                $active = ($this->here == $this->url($item[0][1]));
                if ( $active && isset($options['active'])) {    
                    $listitem = $this->Html->tag($options['active']['tag'], $item[0][0], $options['active']['attributes']);                    
                } else {
                    if ($active) {
                        if (is_array($item[0][2])) {
                            if (isset($item[0][2]['class'])) {
                                $item[0][2]['class'] .= ' active';
                            } else {
                                $item[0][2]['class'] = 'active';
                            }
                        } else {
                            $item[0][2] = array('class' => 'active');
                        }
                    }
                    $listitem = $this->Html->link($item[0][0], $item[0][1], $item[0][2], $item[0][3], $item[0][4]);
                }
            } elseif (isset($item[2])) {
                $listitem = $item[2];
            } else {
                continue;
            }

            if (isset($item[1]['div']) && $item[1]['div'] !== false) {
                if (!is_array($item[1]['div'])) {
                    $item[1]['div'] = array();
                }
                $listitem = $this->Html->tag('div', $listitem, $item[1]['div']);
            }
            
            $list .= $this->Html->tag('li', $listitem, $liAttributes);
        }
        
        /* Generate menu */
        $out .= $this->Html->tag('ul', $list, $ulAttributes);
        
        /* Add optional outer div */
        if (isset($options['div']) && $options['div'] !== false) {
            if (!is_array($options['div'])) {
                $options['div'] = array();
            }
            $out = $this->Html->tag('div', $out, $options['div']);
        }
        return $out;
    }

}?> 